
public class Cylinder extends Primitive{
	
	private Vector start = null;
	private Vector direction = null;
	private double length;
	private double radius;
	
	public Cylinder(){
		this.start = new Vector();
		this.direction = new Vector();
		this.length = 0;
		this.radius = 0;
		this.setSurface(new Surface());
	}
	
	public Cylinder( Vector start, Vector direction, double length, double radius){
		this();
		this.start = start;
		this.direction = direction;
		this.length = length;
		this.radius = radius;
	}
	
	public Cylinder( Vector start, Vector direction, double length, double radius, Surface surface){
		this(start, direction, length, radius);
		this.setSurface(surface);
	}
	
	public void setStart(Vector start) {
		this.start = start;
	}
	
	public Vector getStart(){
		return start;
	}
	
	public void setDirection(Vector direction) {
		this.direction = direction;
	}

	public Vector getDirection(){
		return direction;
	}
	
	public void setLength(double length) {
		this.length = length;
	}

	public double getLength(){
		return length;
	}
	
	public void setRadius(double radius) {
		this.radius = radius;
	}

	public double getRadius(){
		return radius;
	}

	// http://www.gamedev.net/topic/467789-raycylinder-intersection/
	
	// Ray : P(t) = O + V * t
	// Cylinder [O, D, r].
	// point Q on cylinder if ((Q - O) x D)^2 = r^2
	//
	// Cylinder [A, B, r].
	// Point P on infinite cylinder if ((P - A) x (B - A))^2 = r^2 * (B - A)^2
	// expand : ((O - A) x (B - A) + t * (V x (B - A)))^2 = r^2 * (B - A)^2
	// equation in the form (X + t * Y)^2 = d
	// where : 
	// X = (O - A) x (B - A)
	// Y = V x (B - A)
	// d = r^2 * (B - A)^2
	// expand the equation :
	// t^2 * (Y . Y) + t * (2 * (X . Y)) + (X . X) - d = 0
	// => second order equation in the form : a*t^2 + b*t + c = 0 where
	// a = (Y . Y)
	// b = 2 * (X . Y)
	// c = (X . X) - d
	//--------------------------------------------------------------------------
	//Vector AB = (B - A);
	//Vector AO = (O - A);
	//Vector AOxAB = (AO ^ AB); // cross product
	//Vector VxAB = (V ^ AB); // cross product
	//float ab2 = (AB * AB); // dot product
	//float a = (VxAB * VxAB); // dot product
	//float b = 2 * (VxAB * AOxAB); // dot product
	//float c = (AOxAB * AOxAB) - (r*r * ab2);

	// solve second order equation : a*t^2 + b*t + c = 0
	
	public Vector intersectionPoint_direction(Vector source, Vector rayDir ){
		Vector AB = this.direction;//Vector.sub(direction, start);
		Vector AO = Vector.sub(source, this.start);
		Vector AOxAB = Vector.crossProduct(AO , AB); // cross product
		Vector VxAB = Vector.crossProduct(rayDir , AB); // cross product
		double ab2 = Vector.dotProduct(AB , AB); // dot product
		double a = Vector.dotProduct(VxAB , VxAB); // dot product
		double b = 2 * Vector.dotProduct(VxAB , AOxAB); // dot product
		double c = Vector.dotProduct(AOxAB , AOxAB) - (this.radius*this.radius*ab2);
		
		double delta = b*b - 4*a*c;
		if( delta < 0) 
			return null;
		double rootDelta = Math.sqrt(delta);
		double sol1 = ( -b + rootDelta) / (2*a) ;
		double sol2 = ( -b - rootDelta) / (2*a) ;
		Vector intersectionPoint1 = Vector.add(source, rayDir.mul(sol1));
		Vector intersectionPoint2 = Vector.add(source, rayDir.mul(sol2));
		double len1 = heightFromBase(intersectionPoint1);
		double len2 = heightFromBase(intersectionPoint2);
		
		Vector closest_IntersectionPoint;
		
		if (len1 > this.length || intersectionPoint1.isUnderPlain(this.start, this.direction)){
			if(len2 > this.length || intersectionPoint2.isUnderPlain(this.start, this.direction)){
				return null;
			}
			else{
				closest_IntersectionPoint = intersectionPoint2;
			}
		}
		else{
			if(len2 > this.length || intersectionPoint2.isUnderPlain(this.start, this.direction)){
				closest_IntersectionPoint = intersectionPoint1;	
			}
			else{ //both legal

				if ( Vector.getDistance(intersectionPoint1, source) <= 
						Vector.getDistance(intersectionPoint2, source)){
					closest_IntersectionPoint = intersectionPoint1;
				}else {
					closest_IntersectionPoint =  intersectionPoint2;
				}
			}
		}
		
		double Var = (Vector.dotProduct(rayDir, closest_IntersectionPoint) - Vector.dotProduct(rayDir, source))
		/Vector.dotProduct(rayDir, rayDir);
		if (Var < -1E-4) return null;
		else return closest_IntersectionPoint;
		
	}
	
	public double heightFromBase(Vector point){
		return Math.sqrt(
				Vector.getDistance_Q(point, this.start) -
				Vector.pointToLineDistance_Q(point, this.start, this.direction));
	}

	@Override
	public Vector getNormalAt(Vector target, Vector viewDirection) {
		double var = Vector.dotProduct(Vector.sub(target, this.start),this.direction) / 
						Vector.dotProduct(this.direction, this.direction);
		
		Vector diskOrigin = Vector.add(this.start,this.direction.mul(var));
		return Vector.sub_n(target, diskOrigin);
	}
	
	public static Cylinder getDBGcyl(){
		Cylinder cyl = new Cylinder(new Vector(-2,-2,-2), new Vector(0,1,0),4,1);
		cyl.setMtlDiffuse(new RGB(1,0.7,0.7));
		return cyl;
	}

	@Override
	public Vector getLowestPoint() {
		double[] c = start.getCoords();
		double l = radius + length/2;
		return new Vector(c[0] - l, c[1] - l , c[2] - l);
	}

	@Override
	public Vector getHighestPoint() {
		double[] c = start.getCoords();
		double l = radius + length/2;
		return new Vector(c[0] + l, c[1] + l , c[2] + l);
	}
}
